/*
 * Copyright 2009-2021 Ping Identity Corporation
 * All Rights Reserved.
 */
/*
 * Copyright 2009-2021 Ping Identity Corporation
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
/*
 * Copyright (C) 2009-2021 Ping Identity Corporation
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License (GPLv2 only)
 * or the terms of the GNU Lesser General Public License (LGPLv2.1 only)
 * as published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, see <http://www.gnu.org/licenses>.
 */
package com.unboundid.android.ldap.client;



import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;

import android.app.Activity;
import android.app.ProgressDialog;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Spinner;

import static com.unboundid.android.ldap.client.Logger.*;
import static com.unboundid.util.StaticUtils.*;



/**
 * This class provides an Android activity that may be used to edit an existing
 * directory server instance.
 */
public final class EditServer
       extends Activity
       implements OnClickListener, ServerTestInvoker
{
  /**
   * The name of the field used to hold the serialized instance.
   */
  public static final String BUNDLE_FIELD_INSTANCE = "EDIT_SERVER_INSTANCE";



  /**
   * The name of the field used to define the server ID.
   */
  public static final String BUNDLE_FIELD_ID = "EDIT_SERVER_ID";



  /**
   * The name of the field used to define the server address.
   */
  public static final String BUNDLE_FIELD_HOST = "EDIT_SERVER_HOST";



  /**
   * The name of the field used to define the server port.
   */
  public static final String BUNDLE_FIELD_PORT = "EDIT_SERVER_PORT";



  /**
   * The name of the field used to define the security method.
   */
  public static final String BUNDLE_FIELD_SECURITY = "EDIT_SERVER_SECURITY";



  /**
   * The name of the field used to define the bind DN.
   */
  public static final String BUNDLE_FIELD_BIND_DN = "EDIT_SERVER_BIND_DN";



  /**
   * The name of the field used to define the bind password.
   */
  public static final String BUNDLE_FIELD_BIND_PW = "EDIT_SERVER_BIND_PW";



  /**
   * The name of the field used to define the base DN.
   */
  public static final String BUNDLE_FIELD_BASE_DN = "EDIT_SERVER_BASE_DN";



  /**
   * The tag that will be used for log messages generated by this class.
   */
  private static final String LOG_TAG = "EditServer";



  // Indicates whether to use SSL.
  private boolean useSSL = false;

  // Indicates whether to use StartTLS.
  private boolean useStartTLS = false;

  // The server port.
  private int port = 389;

  // The progress dialog displayed while the search is in progress.
  private volatile ProgressDialog progressDialog = null;

  // The server instance being edited.
  private ServerInstance instance = null;

  // The server base DN.
  private String baseDN = "";

  // The bind DN.
  private String bindDN = "";

  // The bind password.
  private String bindPW = "";

  // The server address.
  private String host = "";

  // The server ID.
  private String id = "";



  /**
   * Performs all necessary processing when this activity is created.
   *
   * @param  state  The state information for this activity.
   */
  @Override()
  protected void onCreate(final Bundle state)
  {
    logEnter(LOG_TAG, "onCreate", state);

    super.onCreate(state);

    final Intent i = getIntent();
    final Bundle extras = i.getExtras();
    restoreState(extras);

    id          = instance.getID();
    host        = instance.getHost();
    port        = instance.getPort();
    useSSL      = instance.useSSL();
    useStartTLS = instance.useStartTLS();
    bindDN      = instance.getBindDN();
    bindPW      = instance.getBindPassword();
    baseDN      = instance.getBaseDN();
  }



  /**
   * Performs all necessary processing when this activity is started or resumed.
   */
  @Override()
  protected void onResume()
  {
    logEnter(LOG_TAG, "onResume");

    super.onResume();

    setContentView(R.layout.layout_define_server);
    setTitle(R.string.activity_label);

    // Populate the server ID and make it disabled so it cannot be edited.
    final EditText idField =
         (EditText) findViewById(R.id.layout_define_server_field_id);
    idField.setText(id);
    idField.setEnabled(false);

    // Populate the server address.
    final EditText hostField =
         (EditText) findViewById(R.id.layout_define_server_field_host);
    hostField.setText(host);

    // Populate the server port.
    final EditText portField =
         (EditText) findViewById(R.id.layout_define_server_field_port);
    portField.setText(String.valueOf(port));

    // Populate the list of available security mechanisms.
    final Spinner secSpinner =
         (Spinner) findViewById(R.id.layout_define_server_spinner_security);
    final ArrayAdapter<CharSequence> secAdapter =
         ArrayAdapter.createFromResource(this, R
              .array.edit_server_security_type_list,
              android.R.layout.simple_spinner_item);
    secAdapter.setDropDownViewResource(
         android.R.layout.simple_spinner_dropdown_item);
    secSpinner.setAdapter(secAdapter);
    if (useSSL)
    {
      secSpinner.setSelection(1);
    }
    else if (useStartTLS)
    {
      secSpinner.setSelection(2);
    }
    else
    {
      secSpinner.setSelection(0);
    }

    // Populate the bind DN.
    final EditText bindDNField =
         (EditText) findViewById(R.id.layout_define_server_field_bind_dn);
    bindDNField.setText(bindDN);

    // Populate the bind Password.
    final EditText bindPWField =
         (EditText) findViewById(R.id.layout_define_server_field_bind_pw);
    bindPWField.setText(bindPW);

    // Populate the base DN.
    final EditText baseDNField =
         (EditText) findViewById(R.id.layout_define_server_field_base);
    baseDNField.setText(baseDN);

    // Add an on-click listener to the test and save buttons.
    final Button testButton =
         (Button) findViewById(R.id.layout_define_server_button_server_test);
    testButton.setOnClickListener(this);

    final Button saveButton =
         (Button) findViewById(R.id.layout_define_server_button_server_save);
    saveButton.setOnClickListener(this);
  }



  /**
   * Performs all necessary processing when the instance state needs to be
   * saved.
   *
   * @param  state  The state information to be saved.
   */
  @Override()
  protected void onSaveInstanceState(final Bundle state)
  {
    logEnter(LOG_TAG, "onSaveInstanceState", state);

    saveState(state);
  }



  /**
   * Performs all necessary processing when the instance state needs to be
   * restored.
   *
   * @param  state  The state information to be restored.
   */
  @Override()
  protected void onRestoreInstanceState(final Bundle state)
  {
    logEnter(LOG_TAG, "onRestoreInstanceState", state);

    restoreState(state);
  }



  /**
   * Takes any appropriate action after a button has been clicked.
   *
   * @param  view  The view for the button that was clicked.
   */
  public void onClick(final View view)
  {
    logEnter(LOG_TAG, "onClick", view);

    // Figure out which button was clicked and take the appropriate action.
    switch (view.getId())
    {
      case R.id.layout_define_server_button_server_test:
        testSettings();
        break;

      case R.id.layout_define_server_button_server_save:
        saveSettings();
        break;

      default:
        // This should not happen, but if it does then no action will be taken.
        break;
    }
  }



  /**
   * Creates a new {@code ServerInstance} structure from the provided
   * information.
   *
   * @return  The created {@code ServerInstance} structure.
   *
   * @throws  NumberFormatException  If the port number is not an integer.
   */
  private ServerInstance createInstance()
          throws NumberFormatException
  {
    logEnter(LOG_TAG, "createInstance");

    final EditText idField =
         (EditText) findViewById(R.id.layout_define_server_field_id);
    final String serverID = idField.getText().toString();

    final EditText hostField =
         (EditText) findViewById(R.id.layout_define_server_field_host);
    host = hostField.getText().toString();

    final EditText portField =
         (EditText) findViewById(R.id.layout_define_server_field_port);
    port = Integer.parseInt(portField.getText().toString());

    useSSL = false;
    useStartTLS = false;
    final Spinner secSpinner =
         (Spinner) findViewById(R.id.layout_define_server_spinner_security);
    switch (secSpinner.getSelectedItemPosition())
    {
      case 1:
        useSSL = true;
        break;
      case 2:
        useStartTLS = true;
        break;
      default:
        // No security.
        break;
    }

    final EditText bindDNField =
         (EditText) findViewById(R.id.layout_define_server_field_bind_dn);
    bindDN = bindDNField.getText().toString();

    final EditText bindPWField =
         (EditText) findViewById(R.id.layout_define_server_field_bind_pw);
    bindPW = bindPWField.getText().toString();

    final EditText baseField =
         (EditText) findViewById(R.id.layout_define_server_field_base);
    baseDN = baseField.getText().toString();

    return logReturn(LOG_TAG, "createInstance", new ServerInstance(serverID,
         host, port, useSSL, useStartTLS, bindDN, bindPW, baseDN));
  }



  /**
   * Tests the provided server settings to determine if they are acceptable.
   */
  private void testSettings()
  {
    logEnter(LOG_TAG, "testSettings");

    // Create and start a thread to test the server settings.
    final TestServerThread testThread = new TestServerThread(this, instance);
    testThread.start();

    // Create a progress dialog to display while the search is in progress.
    progressDialog = new ProgressDialog(this);
    progressDialog.setTitle(
         getString(R.string.edit_server_progress_text));
    progressDialog.setIndeterminate(true);
    progressDialog.setCancelable(false);
    progressDialog.setProgressStyle(ProgressDialog.STYLE_SPINNER);
    progressDialog.show();
  }



  /**
   * {@inheritDoc}
   */
  @Override()
  public void testCompleted(final boolean acceptable,
                            final LinkedList<String> reasons)
  {
    logEnter(LOG_TAG, "testCompleted", acceptable, reasons);

    progressDialog.dismiss();

    if (acceptable)
    {
      final Intent i = new Intent(this, PopUp.class);
      i.putExtra(PopUp.BUNDLE_FIELD_TITLE,
           getString(R.string.edit_server_popup_title_success));
      i.putExtra(PopUp.BUNDLE_FIELD_TEXT,
           getString(R.string.edit_server_popup_text_success));
      startActivity(i);
    }
    else
    {
      final Intent i = new Intent(this, PopUp.class);
      i.putExtra(PopUp.BUNDLE_FIELD_TITLE,
           getString(R.string.edit_server_popup_title_failed));
      i.putExtra(PopUp.BUNDLE_FIELD_TEXT,
           getString(R.string.edit_server_popup_text_failed,
                listToString(reasons)));
      startActivity(i);
    }
  }



  /**
   * Saves the provided server settings.
   */
  private void saveSettings()
  {
    logEnter(LOG_TAG, "saveSettings");

    boolean acceptable;
    final LinkedList<String> reasons = new LinkedList<String>();

    ServerInstance newInstance = null;
    try
    {
      newInstance   = createInstance();
      acceptable = newInstance.isDefinitionValid(this, reasons);
    }
    catch (final NumberFormatException nfe)
    {
      logException(LOG_TAG, "saveSettings", nfe);

      acceptable = false;
      reasons.add(getString(R.string.edit_server_err_port_not_int));
    }

    if (acceptable)
    {
      final String instanceID = newInstance.getID();

      try
      {
        final LinkedHashMap<String,ServerInstance> instanceMap =
             new LinkedHashMap<String,ServerInstance>(
                      ServerInstance.getInstances(this));
        instanceMap.put(instanceID, newInstance);
        ServerInstance.saveInstances(this, instanceMap);
        instance = newInstance;
        finish();
        return;
      }
      catch (final Exception e)
      {
        logException(LOG_TAG, "saveSettings", e);

        reasons.add(getExceptionMessage(e));
      }
    }

    final Intent i = new Intent(this, PopUp.class);
    i.putExtra(PopUp.BUNDLE_FIELD_TITLE,
         getString(R.string.edit_server_popup_title_failed));
    i.putExtra(PopUp.BUNDLE_FIELD_TEXT,
         getString(R.string.edit_server_popup_text_failed,
              listToString(reasons)));
    startActivity(i);
  }



  /**
   * Restores the state of this activity from the provided bundle.
   *
   * @param  state  The bundle containing the state information.
   */
  private void restoreState(final Bundle state)
  {
    logEnter(LOG_TAG, "restoreState", state);

    instance = (ServerInstance) state.getSerializable(BUNDLE_FIELD_INSTANCE);

    id = state.getString(BUNDLE_FIELD_ID);
    if (id == null)
    {
      id = "";
    }

    host = state.getString(BUNDLE_FIELD_HOST);
    if (host == null)
    {
      host = "";
    }

    port = state.getInt(BUNDLE_FIELD_PORT, 389);

    switch (state.getInt(BUNDLE_FIELD_SECURITY))
    {
      case 1:
        useSSL = true;
        useStartTLS = false;
        break;
      case 2:
        useSSL = false;
        useStartTLS = true;
        break;
      default:
        useSSL = false;
        useStartTLS = false;
        break;
    }

    bindDN = state.getString(BUNDLE_FIELD_BIND_DN);
    if (bindDN == null)
    {
      bindDN = "";
    }

    bindPW = state.getString(BUNDLE_FIELD_BIND_PW);
    if (bindPW == null)
    {
      bindPW = "";
    }

    baseDN = state.getString(BUNDLE_FIELD_BASE_DN);
    if (baseDN == null)
    {
      baseDN = "";
    }
  }



  /**
   * Saves the state of this activity to the provided bundle.
   *
   * @param  state  The bundle containing the state information.
   */
  private void saveState(final Bundle state)
  {
    logEnter(LOG_TAG, "saveState", state);

    state.putSerializable(BUNDLE_FIELD_INSTANCE, instance);

    final EditText idField =
         (EditText) findViewById(R.id.layout_define_server_field_id);
    id = idField.getText().toString();
    state.putString(BUNDLE_FIELD_ID, id);

    final EditText hostField =
         (EditText) findViewById(R.id.layout_define_server_field_host);
    host = hostField.getText().toString();
    state.putString(BUNDLE_FIELD_HOST, host);

    final EditText portField =
         (EditText) findViewById(R.id.layout_define_server_field_port);
    try
    {
      port = Integer.parseInt(portField.getText().toString());
    }
    catch (final NumberFormatException nfe)
    {
      logException(LOG_TAG, "saveState", nfe);

      port = 389;
    }
    state.putInt(BUNDLE_FIELD_PORT, port);

    final Spinner secSpinner =
         (Spinner) findViewById(R.id.layout_define_server_spinner_security);
    final int secVal = secSpinner.getSelectedItemPosition();
    switch (secVal)
    {
      case 1:
        useSSL      = true;
        useStartTLS = false;
        break;
      case 2:
        useSSL      = false;
        useStartTLS = true;
        break;
      default:
        useSSL      = false;
        useStartTLS = false;
        break;
    }
    state.putInt(BUNDLE_FIELD_SECURITY, secVal);

    final EditText bindDNField =
         (EditText) findViewById(R.id.layout_define_server_field_bind_dn);
    bindDN = bindDNField.getText().toString();
    state.putString(BUNDLE_FIELD_BIND_DN, bindDN);

    final EditText bindPWField =
         (EditText) findViewById(R.id.layout_define_server_field_bind_pw);
    bindPW = bindPWField.getText().toString();
    state.putString(BUNDLE_FIELD_BIND_PW, bindPW);

    final EditText baseDNField =
         (EditText) findViewById(R.id.layout_define_server_field_base);
    baseDN = baseDNField.getText().toString();
    state.putString(BUNDLE_FIELD_BASE_DN, baseDN);
  }



  /**
   * Retrieves a string representation of the contents of the provided list.
   *
   * @param  l  The list from which to take the elements.
   *
   * @return  A string representation of the contents of the provided list.
   */
  private static String listToString(final List<String> l)
  {
    logEnter(LOG_TAG, "listToString", l);

    final StringBuilder buffer = new StringBuilder();
    for (final String s : l)
    {
      buffer.append(EOL);
      buffer.append(EOL);
      buffer.append("- ");
      buffer.append(s);
    }

    return logReturn(LOG_TAG, "listToString", buffer.toString());
  }
}
